<template>
  <div class="call-container">
    <!--  毛玻璃遮罩  -->
    <div v-if="isRinging" class="mask">
      <div v-if="!isCaller" class="operator-area">
        <div class="caller-info">{{ fromUser }} 正在呼叫，是否接听?</div>
        <div class="actions">
          <div class="reject" @click="rejectCall">
            <img :src="RejectIcon" alt="Reject" />
          </div>
          <div class="accept" @click="acceptCall">
            <img :src="AcceptIcon" alt="Accept" />
          </div>
        </div>
      </div>
    </div>
    <!-- 通话时长   -->
    <div v-show="isCalling" class="call-duration">{{ callDuration }}</div>
    <div v-show="isCalling || isCaller" class="hang-up" @click="hangUpCall">
      <img :src="RejectIcon" alt="Hang Up" />
    </div>
    <div
      :class="{
        'big-video-container': isRinging,
        'small-video-container': !isRinging
      }"
    >
      <video ref="localVideo" class="video" playsinline />
    </div>
    <div
      :class="{
        'big-video-container': !isRinging,
        'small-video-container': isRinging
      }"
    >
      <video ref="remoteVideo" class="video" playsinline />
    </div>
    <!-- 视频通话质量信息   -->
    <div v-if="rtt" class="rtt">{{ rtt }}ms</div>
  </div>
</template>

<script setup>
import { onMounted, ref, watch } from "vue";
import router from "@/router/index";
import wsClient from "@/ws/wsClient";

import Ranging from "@/assets/audio/ringing.mp3";

import RejectIcon from "@/assets/call/reject.png";
import AcceptIcon from "@/assets/call/accept.png";

// 当前状态 调试使用
const currentAction = ref([]);

// 外部传递参数
const params = ref({});
// 是否为发起人
const isCaller = ref(false);
// 发起人
const fromUser = ref("");
// 接收人
const toUser = ref("");

// 本人 video 标签实例
const localVideo = ref(null);
// 对方 video 标签实例
const remoteVideo = ref(null);
// 本人视频流
let localStream = null;
// 对方视频流
let remoteStream = null;

// peerConnection 对象
let peer;

const rtt = ref(0);

// 铃声播放
const audio = new Audio(Ranging);

// 是否正在 ringing
const isRinging = ref(false);
// 是否正在通话
const isCalling = ref(false);

// ========================== 发起方处理逻辑 ==============================

// 接收初始化参数
const receiveInitParams = async () => {
  params.value = router.currentRoute.value.query;
  isCaller.value = params.value.isCaller === "true";
  fromUser.value = params.value.fromUser;
  toUser.value = params.value.toUser;
  localStream = await getLocalStream();
  localVideo.value.srcObject = localStream;
  localVideo.value.play();
  isRinging.value = true;
  if (isCaller.value) {
    currentAction.value.push("发起方准备通话");
    await startCall();
  } else {
    currentAction.value.push("接收方准备通话");
    await receiverStart(params);
  }
};

const startCall = async () => {
  // 创建 peerConnection 对象
  peer = newRTCPeerConnection();
  peer.addStream(localStream);

  // 监听 onicecandidate 事件
  // 获取 candidate 信息
  peer.onicecandidate = event => {
    if (event.candidate) {
      console.log("aaa: 主叫onicecandidate: ", event.candidate);
      currentAction.value.push("发起方发送candidate");
      // 发送 candidate 信息
      wsClient.sendMessage({
        action: "webrtc.candidate",
        data: {
          toUid: toUser.value,
          payload: {
            candidate: {
              sdpMLineIndex: event.candidate.sdpMLineIndex,
              sdpMid: event.candidate.sdpMid,
              candidate: event.candidate.candidate
            }
          }
        }
      });
    }
  };

  // 监听 onaddstream 事件
  // 获取远程视频流
  peer.onaddstream = event => {
    console.log("aaa: onaddstream: ", event.stream);
    remoteStream = event.stream;
    remoteVideo.value.srcObject = remoteStream;
    remoteVideo.value.play();
    // 正在通话
    isCalling.value = true;
    startCallDurationTimer();
  };

  // 生成 offer
  const offer = await peer.createOffer({
    offerToReceiveAudio: 1,
    offerToReceiveVideo: 1
  });
  console.log("Offer created: ", offer);
  // 设置本地描述的 offer
  await peer.setLocalDescription(offer);
  // 获取本地描述
  const description = peer.localDescription;
  // 将 offer 发送给接收方
  currentAction.value.push("发起方发送offer");
  await wsClient.sendMessage({
    action: "webrtc.offer",
    data: {
      ts: Date.now(),
      toUid: toUser.value,
      // sdp: offer
      payload: {
        sd: { sdp: description.sdp, type: description.type },
        media: "video"
      }
    }
  });
};

const receiverStart = async params => {
  const message = JSON.parse(params.value.message);
  console.log("aaa: Received offer: ", message);
  currentAction.value.push("接收方收到offer");
  const { data } = message;
  const { payload } = data;
  // 创建 peerConnection 对象
  peer = newRTCPeerConnection();
  peer.addStream(localStream);

  // 监听 onicecandidate 事件
  // 获取 candidate 信息
  peer.onicecandidate = event => {
    if (event.candidate) {
      console.log("aaa: 被叫onicecandidate: ", event.candidate);
      currentAction.value.push("接收方发送candidate");
      // 发送 candidate 信息
      wsClient.sendMessage({
        action: "webrtc.candidate",
        data: {
          ts: Date.now(),
          toUid: fromUser.value,
          payload: {
            candidate: {
              sdpMLineIndex: event.candidate.sdpMLineIndex,
              sdpMid: event.candidate.sdpMid,
              candidate: event.candidate.candidate
            }
          }
        }
      });
    }
  };

  // 监听 onaddstream 事件
  // 获取远程视频流
  peer.onaddstream = event => {
    console.log("aaa: 被叫onaddstream: ", event.stream);
    remoteStream = event.stream;
    remoteVideo.value.srcObject = remoteStream;
    remoteVideo.value.play();
    // 正在通话
    isCalling.value = true;
    startCallDurationTimer();
  };

  // 设置远程描述的 offer
  await peer.setRemoteDescription({
    type: payload.sd.type,
    sdp: payload.sd.sdp
  });
  // 生成 answer
  const answer = await peer.createAnswer();
  console.log("Answer created: ", answer);
  // 设置本地描述的 answer
  await peer.setLocalDescription(answer);
  const description = peer.localDescription;
  // 将 answer 发送给发起方
  currentAction.value.push("接收方发送answer");
  await wsClient.sendMessage({
    action: "webrtc.answer",
    data: {
      ts: Date.now(),
      toUid: fromUser.value,
      payload: {
        media: "video",
        // sd = session description
        sd: { sdp: description.sdp, type: description.type }
      }
    }
  });
};

/**
 * 收到接收方的 answer
 */
const onReceiverAnswer = async () => {
  wsClient.registerActionHandler("webrtc.answer", async message => {
    console.log("aaa: Received answer: ", message);
    currentAction.value.push("发起方收到answer");
    isRinging.value = false;
    const { data } = message;
    const { payload } = data;
    const sd = payload["sd"];
    // 设置远程描述的 answer
    await peer.setRemoteDescription({
      type: sd.type,
      sdp: sd.sdp
    });
    // 添加ice信息
    for (const candidate of iceCandidates) {
      await peer.addIceCandidate({
        sdpMLineIndex: candidate.sdpMLineIndex,
        sdpMid: candidate.sdpMid,
        candidate: candidate.candidate
      });
    }
  });
};

/**
 * 收到发起方拒绝通话
 */
const onCallerRejectCall = () => {
  wsClient.registerActionHandler("webrtc.reject", async message => {
    console.log("Received reject: ", message);
    // 发起方收到接收方拒绝通话
    if (!isCaller.value) {
      return;
    }
    isRinging.value = false;
    console.log("Call rejected");
    closeStream();
    window.close();
  });
};

// ========================== 接收方处理逻辑 ==============================

/**
 * 接收方接受通话
 */
const acceptCall = async () => {
  console.log("Call accepted");
  currentAction.value.push("接收方接受通话");
  isRinging.value = false;
  await wsClient.sendMessage({
    action: "webrtc.accept",
    data: {
      ts: Date.now(),
      toUid: fromUser.value,
      mediaType: "VIDEO",
      targetType: "USER"
    }
  });
};

// 接收方拒绝通话
const rejectCall = async () => {
  console.log("Call rejected");
  // 关闭音视频流
  closeStream();
  // 发送拒绝通话
  await wsClient.sendMessage({
    action: "webrtc.reject",
    data: {
      ts: Date.now(),
      toUid: fromUser.value,
      mediaType: "VIDEO",
      targetType: "USER"
    }
  });
  // 关闭当前窗口
  window.close();
};

// ========================== 公共逻辑 ==============================

/**
 * 创建 PeerConnection 对象
 */
const newRTCPeerConnection = () => {
  return new RTCPeerConnection({
    iceServers: [
      {
        /**
         * stun 获取客户端的公网 IP，帮助建立直接的 P2P 连接
         * turn 中继服务器，确保即使在复杂的网络环境下（如对称 NAT）也能通信
         */
        urls: "stun:101.132.44.88:3478",
        username: "user",
        credential: "password"
      },
      {
        urls: "turn:101.132.44.88:3478",
        username: "user",
        credential: "password"
      }
    ],
    /**
     * 控制 ICE 服务器的使用策略
     *  •	all：使用所有提供的 ICE 服务器（STUN 和 TURN）。
     *  •	public：仅使用公共 STUN 服务器。
     *  •	relay：仅使用 TURN 服务器，适用于不希望直接暴露用户 IP 地址的场景。
     */
    iceTransportPolicy: "all",
    /**
     *  •	描述：定义如何在多条媒体轨道之间共享传输通道。可选值包括：
     *  •	balanced：根据需要使用多个通道。
     *  •	max-bundle：尽可能使用最少的通道来发送所有媒体流。
     *  •	max-compat：每个媒体流都使用自己的通道，提供更高的兼容性。
     */
    bundlePolicy: "max-bundle"
  });
};

const iceCandidates = [];

/**
 * onicecandidate 事件回调
 */
const onIceCandidate = () => {
  wsClient.registerActionHandler("webrtc.candidate", async message => {
    currentAction.value.push("收到candidate");
    console.log("aaa: Received candidate: ", message);
    const { data } = message;
    const { payload } = data;
    const { candidate } = payload;
    // 判断远程连接是否已经建立
    if (!peer || !peer.remoteDescription) {
      console.log("aaa: Add ice candidate to queue: ", candidate);
      iceCandidates.push(candidate);
      return;
    }
    // 添加 candidate
    await peer?.addIceCandidate({
      sdpMLineIndex: candidate.sdpMLineIndex,
      sdpMid: candidate.sdpMid,
      candidate: candidate.candidate
    });
  });
};

/**
 * 挂断电话事件
 */
const onHangUp = () => {
  wsClient.registerActionHandler("webrtc.hangup", async message => {
    currentAction.value.push("收到挂断电话");
    isCalling.value = false;
    console.log("Received hangup: ", message);
    console.log("Hang up");
    closeStream();
    console.log("Call ended");
    window.close();
  });
};

/**
 * 获取本地地音视频流
 */
const getLocalStream = async () => {
  return await navigator.mediaDevices.getUserMedia({
    video: true,
    audio: true
  });
};

/**
 * 获取网络统计信息
 */
const getNetworkStats = async () => {
  if (peer) {
    const stats = await peer.getStats();
    stats.forEach(report => {
      if (report.type === "candidate-pair" && report.state === "succeeded") {
        rtt.value = report.currentRoundTripTime * 1000;
      }
    });
  }
};

/**
 * 关闭音视频流
 */
const closeStream = () => {
  if (peer) {
    peer.close();
  }
  if (localStream) {
    localStream.getTracks().forEach(track => track.stop());
  }
  if (remoteStream) {
    remoteStream.getTracks().forEach(track => track.stop());
  }
  localVideo.value.srcObject = null;
  remoteVideo.value.srcObject = null;
};

// 通话时长
const callDuration = ref("00:00");
let callStartTime = null;
let callDurationInterval = null;

const startCallDurationTimer = () => {
  callStartTime = Date.now();
  callDurationInterval = setInterval(() => {
    const elapsed = Date.now() - callStartTime;
    const minutes = Math.floor(elapsed / 60000)
      .toString()
      .padStart(2, "0");
    const seconds = Math.floor((elapsed % 60000) / 1000)
      .toString()
      .padStart(2, "0");
    callDuration.value = `${minutes}:${seconds}`;
  }, 1000);
};

/**
 * 挂断电话
 */
const hangUpCall = async () => {
  console.log("Hang up");
  isCalling.value = false;
  closeStream();
  clearInterval(callDurationInterval);
  await wsClient.sendMessage({
    action: "webrtc.hangup",
    data: {
      toUid: isCaller.value ? toUser.value : fromUser.value,
      mediaType: "VIDEO",
      targetType: "USER",
      payload: {}
    }
  });
  window.close();
};

/**
 * 播放来电铃声
 */
watch(
  () => isRinging.value,
  value => {
    if (value) {
      audio.loop = true;
      // audio.play();
    } else {
      audio.pause();
    }
  }
);

// 日志信息输出
watch(
  () => currentAction.value,
  () => {
    // log 最后一条事件
    if (currentAction.value.length > 0) {
      console.log(currentAction.value[currentAction.value.length - 1]);
    }
  }
);

onMounted(() => {
  receiveInitParams();
  // 监听 answer 事件
  onReceiverAnswer();
  // 监听 candidate 事件
  onIceCandidate();
  // 监听挂断电话事件
  onHangUp();
  // 收到接收方拒绝通话
  onCallerRejectCall();
  // 定时刷新网络统计信息
  setInterval(getNetworkStats, 1000);
});
</script>

<style scoped lang="scss">
.call-container {
  position: relative;
  width: 100vw;
  height: 100vh;

  .call-duration {
    position: absolute;
    top: 10px;
    left: 10px;
    padding: 5px;
    color: #fff;
    background-color: rgb(0 0 0 / 50%);
    border-radius: 5px;
    z-index: 300;
  }

  .hang-up {
    position: absolute;
    top: 85%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 40px;
    height: 40px;
    cursor: pointer;
    background-color: #f44336;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    transition:
      opacity 0.3s ease-in-out,
      transform 0.3s ease-in-out;
    z-index: 300;

    img {
      width: 24px;
      height: 24px;
      object-fit: cover;
    }

    &:hover {
      background-color: #e53935;
    }
  }

  .mask {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 100;
    width: 100vw;
    height: 100vh;
    background-color: rgb(0 0 0 / 50%);
    backdrop-filter: blur(10px);
    transition: backdrop-filter 1s ease-in-out;

    .operator-area {
      position: absolute;
      top: 50%;
      left: 50%;
      box-sizing: border-box;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      width: 80vw;
      max-width: 400px;
      padding: 20px;
      text-align: center;
      background-color: rgb(255 255 255 / 80%);
      border-radius: 10px;
      transform: translate(-50%, -50%);

      .caller-info {
        margin-bottom: 20px;
        font-size: 18px;
        font-weight: 500;
        color: #333;
      }

      .actions {
        display: flex;
        flex-direction: row;
        justify-content: center;
        width: 100%;

        .accept,
        .reject {
          display: flex;
          align-items: center;
          justify-content: center;
          width: 40px;
          height: 40px;
          margin: 0 15px;
          cursor: pointer;
          background-color: #fff;
          border: 2px solid transparent;
          border-radius: 50%;
          transition: all 0.3s ease-in-out;
        }

        .accept {
          background-color: #4caf50;

          &:hover {
            background-color: #45a247;
          }
        }

        .reject {
          background-color: #f44336;

          &:hover {
            background-color: #e53935;
          }
        }

        img {
          width: 24px;
          height: 24px;
          object-fit: cover;
        }
      }
    }
  }

  .big-video-container {
    width: 100%;
    height: 100%;

    .video {
      width: 100%;
      height: 100vh;
      object-fit: cover;
    }
  }

  .small-video-container {
    position: absolute;
    top: 0;
    right: 0;
    width: 160px;
    height: 120px;
    border: 1px solid #fff;
    z-index: 50;
    overflow: hidden;

    .video {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
  }

  .rtt {
    position: absolute;
    bottom: 10px;
    left: 10px;
    padding: 5px;
    color: #fff;
    background-color: rgb(0 0 0 / 50%);
    border-radius: 5px;
  }

  video {
    transform: scaleX(-1);
  }
}
</style>
